﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using HtmlAgilityPack;

namespace OWA2SMTP
{
  public struct Message
  {
    public string Subject;
    public string From;
    public string To;
    public DateTime Sent;
    public string Body;
  }

  public struct MessageInfo
  {
    public string ID;
    public bool Unread;
  }

  class MessageDownloader
  {
    private const string USERAGENT = "User-Agent=Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5";

    private const string PAGE_LOGIN = "auth/logon.aspx";
    private const string PAGE_AUTHENTICATION = "auth/owaauth.dll";
    private const string PAGE_MESSAGE = "?ae=Item&t=IPM.Note&id=";

    private string baseURL { get; set; }
    private string userName { get; set; }
    private string password { get; set; }

    private CookieContainer cookieContainer;
    public List<MessageInfo> messageInfos { get; private set; }
    public List<Message> downloadedMessages { get; private set; }

    public List<MessageInfo> unreadMessages
    {
      get
      {
        List<MessageInfo> result = new List<MessageInfo>();
        foreach (MessageInfo messageInfo in messageInfos)
        {
          if (messageInfo.Unread)
          {
            result.Add(messageInfo);
          }
        }
        return result;
      }
    }

    /// <summary>
    /// Login, download the Inbox page and parse the message IDs
    /// </summary>
    /// <param name="baseURL">URL to OWA</param>
    public MessageDownloader(string baseURL, string userName, string password)
    {
      downloadedMessages = new List<Message>();
      this.baseURL = baseURL;
      this.userName = userName;
      this.password = password;
      Program.ShowMessage("   Downloading messages from \"{0}\" using username \"{1}\"", baseURL, userName);
      Program.ShowMessage("   Login and download Inbox page");
      string inboxPageBody = GetInboxPage();
      Program.ShowMessage("   Extract message IDs and read/unread info");
      this.messageInfos = ExtractMessageInfos(inboxPageBody);
    }

    /// <summary>
    /// Download all messages (only first page of Inbox)
    /// Unread messages are marked as read
    /// </summary>
    public void DownloadAllMessages()
    {
      downloadedMessages.Clear();
      foreach (MessageInfo messageInfo in messageInfos)
      {
        string messagePageBody = GetMessagePage(messageInfo.ID);
        downloadedMessages.Add(ExtractMessage(messagePageBody));
      }
    }

    /// <summary>
    /// Download only unread messages (only first page of Inbox)
    /// Messages are marked as read
    /// </summary>
    public void DownloadUnreadMessages()
    {
      downloadedMessages.Clear();
      foreach (MessageInfo messageInfo in unreadMessages)
      {
        string messagePageBody = GetMessagePage(messageInfo.ID);
        downloadedMessages.Add(ExtractMessage(messagePageBody));
      }
    }

    #region MessagePageHandling
    private Message ExtractMessage(string messagePageBody)
    {
      Message result = new Message();
      HtmlDocument htmlDocument = new HtmlDocument();
      htmlDocument.LoadHtml(messagePageBody);
      result.Subject = GetHTMLText(htmlDocument.DocumentNode, "//td[@class='sub']");
      result.From = GetHTMLText(htmlDocument.DocumentNode, "//td[@class='frm']");
      result.To = GetHTMLText(htmlDocument.DocumentNode, "//div[@id='divTo']");
      result.Body = GetHTMLText(htmlDocument.DocumentNode, "//div[@class='bdy']");

      string sentString = GetHTMLText(htmlDocument.DocumentNode, "//td[@class='hdtxnr']");
      result.Sent = DateTime.ParseExact(sentString, "f", null);
      Program.ShowMessage("      Downloaded e-mail from \"{0}\", subject \"{1}\"", result.From, result.Subject);
      return result;
    }

    private string GetMessagePage(string messageID)
    {
      string result = "";
      string url = baseURL + PAGE_MESSAGE + System.Web.HttpUtility.UrlEncode(messageID);
      HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
      httpWebRequest.UserAgent = USERAGENT;
      httpWebRequest.CookieContainer = cookieContainer;
      httpWebRequest.KeepAlive = true;
      httpWebRequest.Referer = baseURL;
      httpWebRequest.Method = "GET";

      //do actual request
      HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();

      //read body from response
      Stream stream = httpWebResponse.GetResponseStream();
      StreamReader streamReader = new StreamReader(stream);
      result = streamReader.ReadToEnd();
      streamReader.Close();
      stream.Close();
      return result;
    }
    #endregion

    #region InboxPageHandling
    private string GetInboxPage()
    {
      string result = "";
      //prepare request
      HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(baseURL + PAGE_AUTHENTICATION);
      httpWebRequest.UserAgent = USERAGENT;
      httpWebRequest.CookieContainer = new CookieContainer();
      httpWebRequest.KeepAlive = true;
      httpWebRequest.Referer = baseURL + PAGE_LOGIN;
      httpWebRequest.Method = "POST";
      httpWebRequest.ContentType = "application/x-www-form-urlencoded";

      //write content
      string manualContentString = "";
      manualContentString += "destination=" + baseURL;
      manualContentString += "&flags=0";
      manualContentString += "&forcedownlevel=0";
      manualContentString += "&username=" + userName;
      manualContentString += "&password=" + password;
      manualContentString += "&isUtf8=1";


      byte[] contentBytes = Encoding.UTF8.GetBytes(manualContentString);
      httpWebRequest.ContentLength = contentBytes.Length;
      System.IO.Stream dataStream = httpWebRequest.GetRequestStream();
      dataStream.Write(contentBytes, 0, contentBytes.Length);
      dataStream.Close();

      //do actual request and save cookies to container
      HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
      cookieContainer = httpWebRequest.CookieContainer;

      //read body from response
      Stream stream = httpWebResponse.GetResponseStream();
      StreamReader streamReader = new StreamReader(stream);
      result = streamReader.ReadToEnd();
      streamReader.Close();
      stream.Close();

      return result;
    }

    private List<MessageInfo> ExtractMessageInfos(string inboxPageBody)
    {
      List<MessageInfo> result = new List<MessageInfo>();
      HtmlDocument htmlDocument = new HtmlDocument();
      htmlDocument.LoadHtml(inboxPageBody);
      HtmlNodeCollection htmlMessageInfos = htmlDocument.DocumentNode.SelectNodes("//div[@class='cntnt']//input[@name='chkmsg']");
      
      if (htmlMessageInfos == null)
      {
        //uable to extract list of messages --> unable to login or no messages listed
        Program.ShowMessage("      Unable to login or no messages listed");
        return result;
      }

      foreach (HtmlNode htmlMessageInfo in htmlMessageInfos)
      {
        MessageInfo messageInfo = new MessageInfo();

        //get messageInfo ID
        messageInfo.ID = GetHTMLAttribute(htmlMessageInfo, ".", "value");

        //get read/unread info
        string readImage = GetHTMLAttribute(htmlMessageInfo.ParentNode.ParentNode, ".//img[@class='sI']", "src");
        if (readImage.ToLower().Contains("msg-unrd.gif"))
        {
          messageInfo.Unread = true;
        }
        else
        {
          messageInfo.Unread = false;
        }

        result.Add(messageInfo);
      }
      return result;
    }
    #endregion

    #region HTMLAgility
    private string GetHTMLAttribute(HtmlNode node, string xPath, string attributeName)
    {
      string result = "";
      HtmlNode htmlNode = node.SelectSingleNode(xPath);
      if (htmlNode != null)
      {
        HtmlAttribute htmlAttribute = htmlNode.Attributes[attributeName];
        if (htmlAttribute != null)
        {
          result = htmlAttribute.Value;
        }
      }
      return result;
    }

    private string GetHTMLText(HtmlNode node, string xPath)
    {
      string result = "";
      HtmlNode htmlNode = node.SelectSingleNode(xPath);
      if (htmlNode != null)
      {
        result = HtmlEntity.DeEntitize(htmlNode.InnerText).Trim();
        //TODO: Remove html comments, between <!-- -->
      }
      return result;
    }
    #endregion

    #region Serialization
    public void SerializeMessageIDs(string filename)
    {
      System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(List<MessageInfo>));
      TextWriter textWriter = new StreamWriter(filename);
      xmlSerializer.Serialize(textWriter, messageInfos);
      textWriter.Close();
    }

    public void SerializeMessages(string filename)
    {
      System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(List<Message>));
      TextWriter textWriter = new StreamWriter(filename);
      xmlSerializer.Serialize(textWriter, downloadedMessages);
      textWriter.Close();
    }
    #endregion
  }
}